Skip to content

Update plugin ZTools 提供商 v1.0.1#306

Closed
kaineooo wants to merge 1 commit into
ZToolsCenter:mainfrom
kaineooo:plugin/f-provider
Closed

Update plugin ZTools 提供商 v1.0.1#306
kaineooo wants to merge 1 commit into
ZToolsCenter:mainfrom
kaineooo:plugin/f-provider

Conversation

@kaineooo

Copy link
Copy Markdown
Contributor

插件信息

  • 名称: ZTools 提供商
  • 插件ID: f-provider
  • 版本: 1.0.1
  • 描述: OCR + 翻译提供商集合(百度/谷歌/有道/微软翻译、微信 OCR)
  • 作者: kaineooo
  • 类型: 更新

本次变更

  • feat: ZTools OCR + 翻译提供商插件(截图识别 / 代码翻译 / manage)
  • chore: 调整插件命名空间
  • chore: 调整资源url
  • feat: 微信 OCR 原生模块支持 macOS(libwxocr.dylib)
  • feat: 截图识别结果改为独立窗口展示(左图右文 + 拖动缩放)

截图 / 演示

自检清单

  • plugin.json 的 name / title / version / description / author 字段均已检查
  • 已移除调试日志、未使用文件、敏感信息(.env、token、密钥等)
  • 本次 PR 的 diff 仅涉及 plugins/f-provider/ 目录
  • 已在本地 ZTools 客户端实际加载并测试过此插件,主要功能正常
  • 同意以仓库声明的开源协议发布此插件

此 PR 由 ztools-plugin-cli 自动管理:每次 ztools publish 在分支上追加一个 commit,PR 链接保持不变。

- feat: ZTools OCR + 翻译提供商插件(截图识别 / 代码翻译 / manage)
- chore: 调整插件命名空间
- chore: 调整资源url
- feat: 微信 OCR 原生模块支持 macOS(libwxocr.dylib)
- feat: 截图识别结果改为独立窗口展示(左图右文 + 拖动缩放)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the screenshot OCR feature to display results in a dedicated, borderless sub-window instead of inline. It introduces logo-loading helpers, updates Vite to support a multi-page build, and implements interactive features like image dragging, zooming, and bidirectional highlighting. The review feedback highlights several opportunities to improve robustness, such as adding null checks for display and window instances, handling asynchronous clipboard write errors, implementing an image load error handler, and resetting interaction states when a new image is loaded.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +135 to +141
const display = window.ztools.getPrimaryDisplay()
// workArea 同为 DIP,含任务栏扣除,比 size 更安全
const workArea = display.workArea
const screenWDip = workArea.width
const screenHDip = workArea.height
// scaleFactor:如 1 / 1.25 / 1.5 / 1.9 / 2
const scaleFactor = display.scaleFactor || 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在获取屏幕显示信息时,window.ztools.getPrimaryDisplay() 可能会在某些异常情况下(例如多显示器切换、系统休眠唤醒或 API 未完全加载时)返回 nullundefined。为了提高代码的健壮性,建议对 display 进行空值保护,避免因直接读取 display.workArea 而导致程序崩溃。

  const display = window.ztools.getPrimaryDisplay()
  // workArea 同为 DIP,含任务栏扣除,比 size 更安全
  const workArea = display?.workArea || { width: 1280, height: 800, x: 0, y: 0 }
  const screenWDip = workArea.width
  const screenHDip = workArea.height
  // scaleFactor:如 1 / 1.25 / 1.5 / 1.9 / 2
  const scaleFactor = display?.scaleFactor || 1

Comment on lines +222 to +230
function injectData(
win: BrowserWindow.WindowInstance,
data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string }
): void {
try {
// 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串
const payload = JSON.stringify(data)
const code = `window.__loadScreenOcrResult && window.__loadScreenOcrResult(${payload});`
win.webContents.executeJavaScript(code)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

injectData 函数中,如果传入的 win 实例为 nullundefined(例如创建窗口失败时),直接访问 win.webContents 会抛出 TypeError。虽然外层有 try-catch 保护,但在函数开头增加显式的空值校验是更优雅且安全的防御性编程实践。

function injectData(
  win: BrowserWindow.WindowInstance,
  data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string }
): void {
  if (!win) return
  try {
    // 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串
    const payload = JSON.stringify(data)
    const code = 'window.__loadScreenOcrResult && window.__loadScreenOcrResult(' + payload + ');'
    win.webContents.executeJavaScript(code)

Comment on lines +74 to +84
function copyText(text: string) {
if (!text) return
try {
navigator.clipboard?.writeText(text)
} catch (_) {
/* 子窗口可能无 clipboard 权限,静默失败 */
}
copied.value = text
if (copiedTimer) window.clearTimeout(copiedTimer)
copiedTimer = window.setTimeout(() => (copied.value = ''), 1200)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

navigator.clipboard.writeText 是一个异步操作,返回一个 Promise。在当前的 try-catch 块中,由于没有使用 await,如果 writeText 发生错误(例如用户未授予剪贴板权限),该错误将无法被同步的 try-catch 捕获,从而导致未捕获的 Promise 拒绝(Unhandled Promise Rejection)。建议将 copyText 改为 async 函数并使用 await

async function copyText(text: string) {
  if (!text) return
  try {
    await navigator.clipboard?.writeText(text)
  } catch (_)
    /* 子窗口可能无 clipboard 权限,静默失败 */
  }
  copied.value = text
  if (copiedTimer) window.clearTimeout(copiedTimer)
  copiedTimer = window.setTimeout(() => (copied.value = ''), 1200)
}

Comment on lines +99 to +113
function loadImage() {
if (!image.value) return
const img = new Image()
img.onload = () => {
naturalWidth.value = img.naturalWidth
naturalHeight.value = img.naturalHeight
lastImg = img
nextTick(() => {
drawToCanvas(img)
// 初次加载按「适应窗口」展示(不一定是 1:1),避免大图溢出
fitView()
})
}
img.src = image.value
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

loadImage 中,如果图片加载失败(例如 base64 数据损坏或格式不支持),img.onload 将不会触发,且没有任何错误处理。这会导致界面一直处于加载状态(loadingtrue)。建议为 img 添加 onerror 事件处理函数,以便在加载失败时更新状态并提示用户。

function loadImage() {
  if (!image.value) return
  const img = new Image()
  img.onload = () => {
    naturalWidth.value = img.naturalWidth
    naturalHeight.value = img.naturalHeight
    lastImg = img
    nextTick(() => {
      drawToCanvas(img)
      // 初次加载按「适应窗口」展示(不一定是 1:1),避免大图溢出
      fitView()
    })
  }
  img.onerror = () => {
    loading.value = false
    errorText.value = '图片加载失败'
  }
  img.src = image.value
}

Comment on lines +167 to +174
watch(image, (src) => {
if (src) loadImage()
else {
naturalWidth.value = 0
naturalHeight.value = 0
lastImg = null
}
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

image 发生变化(例如用户重新截图并加载新结果)时,应该重置上一次截图的交互状态(如 activeIndexhoveredIndexflashedIds)。否则,新图片加载后可能会残留旧的高亮状态或导致索引越界。

watch(image, (src) => {
  activeIndex.value = null
  hoveredIndex.value = null
  flashedIds.value = []
  if (src) loadImage()
  else {
    naturalWidth.value = 0
    naturalHeight.value = 0
    lastImg = null
  }
})

@kaineooo kaineooo closed this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants